package com.x.share.mid.utils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.x.share.mid.http.client.SSLHttpClient;
import com.x.share.mid.prop.WxProperties;

public class WxPayUtils {

	static Logger logger = LoggerFactory.getLogger(WxPayUtils.class);

	// 定义签名，微信根据参数字段的ASCII码值进行排序 加密签名,故使用SortMap进行参数排序
	public static String md5Sign(SortedMap<String, String> params, String key) {
		StringBuilder sb = new StringBuilder();
		Set<Entry<String, String>> es = params.entrySet();
		Iterator<Entry<String, String>> it = es.iterator();
		while (it.hasNext()) {
			Entry<String, String> entry = it.next();
			String k = entry.getKey();
			String v = entry.getValue();
			if (StringUtils.isNotBlank(v) && !k.equals("sign") && !k.equals("key")) {
				sb.append(k).append("=").append(v).append("&");
			}
		}
		sb.append("key=" + key);
		logger.info(">>>> params string: {}", sb.toString());
		String sign = DigestUtils.md5Hex(sb.toString()).toUpperCase();
		logger.info(">>>> sign string: {}", sign);
		return sign;
	}

	/**
	 * 微信支付参数转化为xml格式
	 * 
	 * @param params
	 * @return
	 */
	public static String convert2Xml(SortedMap<String, String> params) {
		StringBuilder sb = new StringBuilder();
		sb.append("<xml>");
		Set<Entry<String, String>> es = params.entrySet();
		Iterator<Entry<String, String>> it = es.iterator();
		while (it.hasNext()) {
			Entry<String, String> entry = it.next();
			String k = entry.getKey();
			String v = entry.getValue();
			if ("sign".equalsIgnoreCase(k)) {
			} else if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k)) {
				sb.append("<").append(k).append(">").append("<![CDATA[").append(v).append("]]></").append(k)
						.append(">");
			} else {
				sb.append("<").append(k).append(">").append(v).append("</").append(k).append(">");
			}
		}
		sb.append("<sign>").append(params.get("sign")).append("</sign>");
		sb.append("</xml>");
		logger.info(">>>> request xml: {}", sb.toString());
		return sb.toString();
	}

	/**
	 * xml 转 Map
	 * 
	 * @param result
	 *            XML格式字符串
	 * @return
	 */
	public static Map<String, Object> xml2Map(String result) {
		Map<String, Object> map = new HashMap<String, Object>();
		InputStream is = null;
		try {
			is = new ByteArrayInputStream(result.getBytes("utf-8"));
			SAXReader reader = new SAXReader();
			Document doc = reader.read(is);
			Element root = doc.getRootElement();
			@SuppressWarnings("unchecked")
			List<Element> list = root.elements();
			for (Element e : list) {
				map.put(e.getName(), e.getText());
			}
		} catch (Exception e) {
		} finally {
			if (is != null) {
				try {
					is.close();
				} catch (IOException e) {
				}
			}
		}
		return map;
	}

	/**
	 * 获取随机串
	 * 
	 * @param length
	 * @return
	 */
	public static String getNonceStr(int length) {
		String chars = "abcdefghijklmnopqrstuvwxyz0123456789";
		char[] str = new char[length];
		Random random = new Random();
		for (int i = 0; i < length; i++) {
			int index = random.nextInt(chars.length());
			str[i] = chars.charAt(index);
		}
		return new String(str);
	}

	/**
	 * 微信异步通知流转化map
	 * 
	 * @param stream
	 * @return
	 */
	public static Map<String, String> stream2Map(InputStream stream) {
		Map<String, String> map = new HashMap<String, String>();
		SAXReader reader = new SAXReader();
		try {
			Document doc = reader.read(stream);
			Element root = doc.getRootElement();
			@SuppressWarnings("unchecked")
			List<Element> list = root.elements();
			for (Element e : list) {
				map.put(e.getName(), e.getText());
			}
			stream.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return map;
	}

	/**
	 * 将map转化为xml且不对参数做任务处理
	 * 
	 * @param map
	 * @return
	 */
	public static String map2xmlInRaw(Map<String, String> map) {
		String xmlResult = "";
		StringBuffer sb = new StringBuffer();
		sb.append("<xml>");
		for (String key : map.keySet()) {
			String value = "<![CDATA[" + map.get(key) + "]]>";
			sb.append("<" + key + ">" + value + "</" + key + ">");
		}
		sb.append("</xml>");
		xmlResult = sb.toString();
		logger.warn("wx pay notify >>>>> :[{}]", xmlResult);
		return xmlResult;
	}

	/**
	 * 微信app支付统一下单接口
	 * 
	 * @param tradeNo
	 *            应用交易号,如订单号
	 * @param body
	 *            交易信息
	 * @param fee
	 *            交易费用,单位分
	 * @param props
	 *            微信支付属性
	 * @return
	 * @throws Exception
	 * @see https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_1
	 */
	public static Map<String, Object> wxAppUnifiedorder(String tradeNo, String body, Integer fee, WxProperties props)
			throws Exception {
		SortedMap<String, String> mp = new TreeMap<String, String>();
		mp.put("appid", props.getOpenAppId());
		mp.put("mch_id", props.getMerchantId());
		mp.put("nonce_str", getNonceStr(10));
		mp.put("body", body);
		mp.put("out_trade_no", tradeNo);
		mp.put("total_fee", String.valueOf(fee));
		mp.put("spbill_create_ip", props.getSpbillIp());
		mp.put("notify_url", props.getNotifyUrl());
		mp.put("trade_type", "APP");
		String sign = md5Sign(mp, props.getMerchantKey());
		mp.put("sign", sign);
		String xml = convert2Xml(mp);
		logger.info(">>>> wx app unified order request: {}", xml);
		SSLHttpClient client = new SSLHttpClient(props.getCertPath(), props.getMerchantId());
		String result = client.post("https://api.mch.weixin.qq.com/pay/unifiedorder", xml);
		logger.info(">>>> wx app unified order response: {}", result);
		Map<String, Object> wxRltMap = xml2Map(result);
		return wxRltMap;
	}

	/**
	 * 微信扫码支付(商品二维码或订单二维码)<br>
	 * <p>
	 * 模式一开发前，商户必须在公众平台后台设置支付回调URL。URL实现的功能：接收用户扫码后微信支付系统回调的productid和openid
	 * </p>
	 * <p>
	 * 模式二与模式一相比，流程更为简单，不依赖设置的回调支付URL。商户后台系统先调用微信支付的统一下单接口，微信后台系统返回链接参数code_url，<br>
	 * 商户后台系统将code_url值生成二维码图片，用户使用微信客户端扫码后发起支付。注意：code_url有效期为2小时，过期后扫码不能再发起支付。<br>
	 * </p>
	 * 
	 * @param tradeNo
	 *            应用交易号,如订单号
	 * @param body
	 *            交易信息
	 * @param fee
	 *            交易费用,单位分
	 * @param props
	 *            微信支付属性
	 * @return
	 * @throws Exception
	 * @see https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4
	 * @see https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5
	 */
	public static Map<String, Object> wxQrCodeUnifiedorder(String tradeNo, String body, Integer fee, WxProperties props)
			throws Exception {
		SortedMap<String, String> mp = new TreeMap<String, String>();
		mp.put("appid", props.getAppId());
		mp.put("mch_id", props.getMerchantId());
		mp.put("nonce_str", getNonceStr(10));
		mp.put("body", body);
		mp.put("out_trade_no", tradeNo);
		mp.put("total_fee", String.valueOf(fee));
		mp.put("spbill_create_ip", props.getSpbillIp());
		mp.put("notify_url", props.getNotifyUrl());
		mp.put("trade_type", "NATIVE");
		String sign = md5Sign(mp, props.getMerchantKey());
		mp.put("sign", sign);
		String xml = convert2Xml(mp);
		logger.info(">>>> wx qr code 1 request: {} >>>", xml);
		SSLHttpClient client = new SSLHttpClient(props.getCertPath(), props.getMerchantId());
		String result = client.post("https://api.mch.weixin.qq.com/pay/unifiedorder", xml);
		logger.info(">>>> wx qr code 1 response: {}", result);
		Map<String, Object> wxRltMap = xml2Map(result);
		return wxRltMap;
	}

	/**
	 * 微信公众号内支付
	 * 
	 * @param tradeNo
	 *            应用交易号,如订单号
	 * @param body
	 *            交易信息
	 * @param fee
	 *            交易费用,单位分
	 * @param props
	 *            微信支付属性
	 * @param openId
	 *            用户公众号内openId
	 * @return
	 * @throws Exception
	 */
	public static Map<String, Object> wxJsApiUnifiedorder(String tradeNo, String body, Integer fee, String openId,
			WxProperties props) throws Exception {
		SortedMap<String, String> mp = new TreeMap<String, String>();
		mp.put("appid", props.getAppId());
		mp.put("mch_id", props.getMerchantId());
		mp.put("nonce_str", getNonceStr(10));
		mp.put("body", body);
		mp.put("out_trade_no", tradeNo);
		mp.put("total_fee", String.valueOf(fee));
		mp.put("spbill_create_ip", props.getSpbillIp());
		mp.put("notify_url", props.getNotifyUrl());
		mp.put("trade_type", "JSAPI");
		mp.put("openid", openId);
		String sign = md5Sign(mp, props.getMerchantKey());
		mp.put("sign", sign);
		String xml = convert2Xml(mp);
		logger.info(">>>> wx qr code 1 request: {} >>>", xml);
		SSLHttpClient client = new SSLHttpClient(props.getCertPath(), props.getMerchantId());
		String result = client.post("https://api.mch.weixin.qq.com/pay/unifiedorder", xml);
		logger.info(">>>> wx qr code 1 response: {}", result);
		Map<String, Object> wxRltMap = xml2Map(result);
		return wxRltMap;
	}

	/**
	 * 微信退款申请接口
	 * @param transId 微信订单号
	 * @param refundId 退款id
	 * @param totalFee 订单总金额(分)
	 * @param refundFee 退款总金额(分)
	 * @param refundDesc 退款原因
	 * @param props 微信支付属性
	 * @return
	 * @throws Exception
	 * @see https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_4&index=6
	 */
	public static Map<String, Object> wxRefund(String transId, String refundId, Integer totalFee,
			Integer refundFee, String refundDesc, WxProperties props) throws Exception {
		SortedMap<String, String> mp = new TreeMap<String, String>();
		mp.put("appid", props.getAppId());
		mp.put("mch_id", props.getMerchantId());
		mp.put("nonce_str", getNonceStr(10));
		mp.put("transaction_id", transId);
		mp.put("out_refund_no", refundId);
		mp.put("total_fee", String.valueOf(totalFee));
		mp.put("refund_fee", String.valueOf(refundFee));
		mp.put("refund_desc", refundDesc);
		String sign = md5Sign(mp, props.getMerchantKey());
		mp.put("sign", sign);
		String xml = convert2Xml(mp);
		logger.info(">>>> wx refund request: {} >>>", xml);
		SSLHttpClient client = new SSLHttpClient(props.getCertPath(), props.getMerchantId());
		String result = client.post("https://api.mch.weixin.qq.com/secapi/pay/refund", xml);
		logger.info(">>>> wx refund response: {}", result);
		Map<String, Object> wxRltMap = xml2Map(result);
		return wxRltMap;
	}

}